"""This module holds classes and functions to implement the xml
tree visualization inheriting from Tree"""

import tree
from numpy import *

def readDom(context, style, dom, min = array((-0.5, -0.5),'f'), max = array((0.5, 0.5),'f'), level = 1):
    """Takes a dom tree (lxml now) and returns a root render node to start
       expanding

            context         GlContext instance
            style           style instance to build geometry
            dom             A lxml etree node
            min, max        Corners of the box to stat with
            level           Depth level, deprecated"""
    root = dom
    computeSizes(root)
    tree = XmlTree(context, style, root, min, max, level)
    #tree.styleProps = nodeStyle(dom, context) #fillStyle( tree.styleProps, {}, configuration )
    return tree

def decodeColor( value ):
    "Takes a string of the form #FF8B2A and returns an rgb float tuple"
    if value[0] == '#': value = int( value[1:], 16 )
    else: value = int( value, 16 )
    R = (value >> 16) & 255
    G = (value >> 8) & 255
    B = value & 255
    return (R/255.0, G/255.0, B/255.0)

def nodeSize( node ):
    "Returns the size(visual importance) of a node from attributes"
    if node.get('size'):
        size = int( node.get('size') )
        return size
    else: return 1.0

def nodeStyle( node, context):
    """Returns the style properties dictionary for the given node and
       if it is not already done, converts string values to an usable
       value for the render."""
    #styleProps = context.getXmlCss( node )
    styleProps = { 'color':'#FF9933', 'textcolor':'#000000' }
    if type(styleProps.get('size',None)) in [str, unicode]:
        styleProps['size'] = float( styleProps['size'] )
    if type(styleProps.get('color',None)) in [str, unicode]:
        styleProps['color'] = decodeColor( styleProps['color'] )
    if type(styleProps.get('textcolor',None)) in [str, unicode]:
        styleProps['textcolor'] = decodeColor( styleProps['textcolor'] )
    if styleProps.get('titlefmt'):
        cont = styleProps.get('titlefmt')
        if len(cont) > 1 and cont[0] == '"' and cont[-1] == '"': styleProps['titlefmt'] = cont[1:-1]
    return styleProps

def computeSizes(node):
    """Recursively computes the size of each node by summing up
       children's size. If it has non-empty text, uses its length
       as well. And if none of this is available, tries to get a size
       attribute or asume 1.0. Finally saves the computed value in 
       'size' attribute and this MUST BE CHANGED"""
    size = 0
    if node.text and node.text.strip(): size += len( node.text.strip() )
    for c in node:
        #if not c.nodeValue and c.tagName == 'div':
        size += computeSizes(c)
    if size: node.attrib['size'] = str( size )
    else:
        if node.get('size'): size = int( node.get('size') )
        else:
            size = 1
            node.attrib['size'] = '1'
    return size

class XmlTree(tree.Tree):
    """Xml render node class. Inherits everything from Tree and has
       only to worry about expanding children and getting titles."""
    
    def __init__(self, context, style, node, min = array((-0.5, -0.5),'f'), max = array((0.5, 0.5),'f'), level = 1, css = None):
        """Init function

                context         GlContext to use
                style           style instance to build geometry
                node            lxml etree node associated with this render node
                min, max        Corners of the box to use
                level           Depth of this node in the actual xml tree
                css             Style properties dictionary if available, otherwise
                                we ask context to get one"""
        self.node = node
        #self.conf = conf
        if css is None: css = nodeStyle( self.node, context )
        text = titleFromNode( node, css )
        if not text: text = '(empty)'
        tree.Tree.__init__(self, context, style, text, min, max, level)
        self.setStyle( css )

    def getChildren(self):
        """Returns the child render nodes for the child xml nodes of this one.
           Distributes every child in the available space using functions from
           tree module"""
        level = self.level+1
        nodes = []

#        if self.node.attributes.has_key('mount'):
#            mount = parseLine(self.node.attributes['mount'].value)
#            mounted = vns.vnsOpen( self.context, self.style, mount, self.geom.min, self.geom.max, self.level )
#            return mounted.getChilds()

        nodes = [ c for c in self.node]
        nodestyles = [ nodeStyle( c, self.context ) for c in nodes ]
        if not nodes: return []
        sizes = [ nodeSize(n) for n in nodes ]
        nodes, sizes, nodestyles = childrenLayout( nodes, sizes, nodestyles )
        if not nodes: return []
        if len(nodes) == 1 and nodes[0].tag == 'data':
            self.context.showInfo( nodes[0] )
            return []
        min, max = self.expandArea()
        boxes = tree.distribute(sizes, tree.Box(min, max) )
        children = [ XmlTree( self.context, self.style, nodes[i], boxes[i].min, boxes[i].max, level, nodestyles[i] ) 
                   for i in xrange(len(nodes)) ]
        #for c in childs: c.setStyle( fillStyle( c.styleProps, self.styleProps, self.conf ) )
        return children

def childrenLayout(nodes, sizes, nodestyles):
    """Makes a correction in the size of the children nodes using
       fixed sizes established in css (nodestyles) and a maximum
       size ratio criteria.

            nodes           List with xml nodes
            sizes           Original precomputed sizes of the nodes
            nodestyles      A list of CSS dictionaries"""
    fixed = []
    free = []
    fixedsize = []
    freesize = []
    fixedcss = []
    freecss = []
    for i in xrange(len(nodes)):
        if nodestyles[i].has_key('hide'): continue
        elif nodestyles[i].has_key('size'):
            fixed.append( nodes[i] )
            fixedsize.append( nodestyles[i]['size'] )
            fixedcss.append( nodestyles[i] )
        else:
            free.append( nodes[i] )
            freesize.append(  sizes[i] )
            freecss.append( nodestyles[i] )
    ensureMaxSizeRatio( freesize, 100 )
    fixedtotsize = sum(fixedsize)
    freetotsize = sum(freesize)
    freespace = 1.0 - fixedtotsize
    nodes = fixed
    sizes = fixedsize
    styles = fixedcss
    nodes += free
    sizes += [ freespace*size/freetotsize for size in freesize ]
    styles += freecss
    return nodes, sizes, styles


def ensureMaxSizeRatio(sizes, ratio):
    """Scales a size value list to ensure that the biggest one is
       no more than 'ratio' times the smallest one."""
    if not sizes: return
    smin = min(sizes)
    smax = max(sizes)
    if (smax / smin) > ratio:
        fact = float(ratio) / (smax / smin)
        for i in xrange(len(sizes)):
            sizes[i] = (sizes[i] - smin) * fact + smin

import re
xpathreg = re.compile('\|[^|]*\|')

def extractXpaths(format):
    """Takes a format string of the form:
            "bla bla |xpath1| bla |xpath2| bla"
       And returns a tuple with an standar C format string and a list of 
       xpaths whose result should be inserted in the format string:
            ("bla bla %s bla %s bla", ["xpath1", "xpath2"])"""
    m = xpathreg.search(format)
    paths = []
    while m:
        path = format[m.start()+1:m.end()-1]
        format = format[:m.start()] +'%s'+ format[m.end():]
        paths.append(path)
        m = xpathreg.search(format)
    return (format, paths)

def getNodeItem( node, path):
    """Returns the xpath query result on the given node if
       it is a string. Otherwise returns the empty string."""
    try: ello = unicode(node.xpath( path ))
    except: return ''
    if type(ello) in [str, unicode]: return ello
    else: return ''

def titleFromNode( node, css ):
    """Returns the title for a node either using its tag name
       or a format string specified in CSS as 'titlefmt'. See
       extractXpaths for details."""
    format = css.get('titlefmt')
    if format:
        format, paths = extractXpaths(format)
        return format % tuple([ getNodeItem( node, path ) for path in paths ])
    else:
        title = node.get('title')
        if title: return title
        else: return node.tag
